package localSearch;


import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.logging.Logger;

import localSearch.LocalSearch;
import localSearch.LocalSearchPopulation;
import random.TestClusterBuilder;
import util.GAConfig;
import util.GAOperationsConfig;
import util.Filter;
import instrumentation.Condition;
import instrumentation.ConditionIf;
import instrumentation.ConditionSwitch;
import instrumentation.CoverageBranchTarget;
import instrumentation.Data;
import instrumentation.TrackerCoverage;
import GA.*;
import Operations.*;
import ReadWriteFieldsRelations.*;

public class HillClimbingPure extends LocalSearchPopulation{
	private static final Logger logger = Logger.getLogger("MAMDA.LocalSearch");

	private  TrackerCoverage tracker;
	private  long startTime;
	private  long elapsed;
	private  long TestTimeout;
	private Map<Integer, Map<Class<?>, List<String>>>  recommendMethods = new LinkedHashMap<Integer, Map<Class<?>, List<String>>>();

	@Override	
	public Chromosome execute()throws  IOException, ClassNotFoundException{
		System.out.println("	HillClimbing No relation	");
		startTime=gaInfo.getStartTime();
		TestTimeout=gaInfo.getTestTimeOut();
		elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
		
		tracker = TrackerCoverage.getTracker();
		
		int trgtBranch=getTargetToCover(tracker);
		if(trgtBranch==-1) return null;
		Chromosome chrom =tracker.branchNotCoveredByIndividual.get(trgtBranch);
	   System.out.println("Branch# :" + trgtBranch );//+ " Original chrom: "+chrom.toString() );
	   if(trgtBranch==43||trgtBranch==345)
		   System.out.print(" execute: " +trgtBranch);

			Chromosome result = hillClimb(chrom,trgtBranch,recommendMethods);
			if(result!=null)
				return result;

		return null;
	}
	public Map<Integer, Map<Class<?>, List<String>>>  getRecommendMethods(){ return null;}
	


	public int getTargetToCover(TrackerCoverage tracker)throws  IOException, ClassNotFoundException{
		int trgtBR=-1;
		Map<Integer,CoverageBranchTarget> trackers=tracker.getTrackers();
		List<Integer> keys = new ArrayList<Integer>(trackers.keySet());
			CoverageBranchTarget condTrgt=null;
		int times=0;
		while(trgtBR==-1){
			times++;
			for ( int indx = 0; indx < trackers.size(); indx++) {
				if(tracker.branchNotCoveredByIndividual.containsKey(keys.get(indx))){ 
					condTrgt = (CoverageBranchTarget) trackers.get(keys.get(indx));
					if(condTrgt.getQuality()==Float.NEGATIVE_INFINITY){
						tracker.branchNotCoveredByIndividual.remove(keys.get(indx));
						continue;
					}
					Condition c =this.getCondition(keys.get(indx));
					if(c.getIsCallMethod()&&times!=2) continue;
					if(condTrgt==null) continue;
					int considerBr=	keys.get(indx);
					if( condTrgt.getAttempt()!=gaInfo.getAttempt()){
						trgtBR=considerBr;

						break;
					}

			}
			}//for indx
			if(tracker.branchNotCoveredByIndividual.size()==0)
				return -1;
			if(trgtBR==-1)
				gaInfo.setAttempt(gaInfo.getAttempt()+1);
			else{
				condTrgt = (CoverageBranchTarget) trackers.get(trgtBR);
				condTrgt.setAttempt(gaInfo.getAttempt());
			}
		}
			return trgtBR;
	
	}
	private Condition getCondition(int trgtBR){
		Map<Integer, Condition> mapBranchCondition=gaInfo.getMapBranchCondition();
	       TreeMap<Integer,Condition> sorted_map = new TreeMap<Integer,Condition>(mapBranchCondition);
	       if(sorted_map.containsKey(trgtBR)){
	   			Condition c= sorted_map.get(trgtBR);
	   			return c;
	       }
		return null;
	}
	private static final int ITERATIONS = 500;
	/** Number of attempts when a method is selected */
	private final int TTL_FIRST = 10;
	/** Number of attempts for operations able to reduce the branch distance */
	private final int TTL_IMPROVEMENT  = 20;
	public Chromosome hillClimb (Chromosome originalIndividual,int trgtBR,Map<Integer, Map<Class<?>, List<String>>>  methodsRecommendPValues){
		
		int ttl = 0;//how many times that a position/ method can be targeted again
		Map<Integer,CoverageBranchTarget> trackers=tracker.getTrackers();
		CoverageBranchTarget condTarget = (CoverageBranchTarget) trackers.get(trgtBR);
		if(condTarget==null) return null;

		float oldBranchDistFitness=condTarget.getQuality();		
		Chromosome individualMuted=null;
		for (int i = 0; i < ITERATIONS && elapsed < TestTimeout&& this.gaInfo.getNoExecution()<=GAConfig.fitnessEvaluation; i++) {
		if(--ttl < 0) {
			individualMuted=originalIndividual.clone();	
			ttl = TTL_FIRST;
		}

//		System.out.println("\t originalIndividual. : "+ originalIndividual.toString() +" " + " fit: "+ originalIndividual.getFitness() + " BRdist: "+ originalIndividual.getBranchDistance());

		// the mutate operations must change the size of the individual, except in the modify operations that returns the same size of the indiviuals with the original
			boolean canContinue = true;
			while(canContinue && individualMuted.getSize()==originalIndividual.getSize())
				canContinue=mutate(individualMuted,trgtBR);

//		System.out.println("\t muteted. : "+ individualMuted.toString() +" " + " fit: "+ individualMuted.getFitness() + " BRdist: "+ individualMuted.getBranchDistance());



			tracker.reset();
			ReflectionExecutor.execute( gaInfo,individualMuted);
			tracker.calculateFitness(individualMuted);
			this.gaInfo.setNoExecution((this.gaInfo.getNoExecution()+1));

//			if(trgtBR==39){
//			System.out.println("\t originalIndividual. : "+ originalIndividual.getSize() +" " + " fit: "+ originalIndividual.getFitness() + " BRdist: "+ originalIndividual.getBranchDistance());

//				System.out.println("\t muteted. : "+ individualMuted.getSize() +" " + " fit: "+ individualMuted.getFitness() + " BRdist: "+ individualMuted.getBranchDistance());
//			}
			

			if(condTarget.getQuality() == Float.NEGATIVE_INFINITY) {
					logger.info("Target " + condTarget.toString() + " hit");
						return individualMuted;
			}
			
			if(oldBranchDistFitness==condTarget.getQuality())
				ttl = 0;
			else{
					ttl = TTL_IMPROVEMENT;
					oldBranchDistFitness=condTarget.getQuality();
					originalIndividual=individualMuted.clone();
					originalIndividual.setBranchDistance(individualMuted.getBranchDistance());
					originalIndividual.setFitness(individualMuted.getFitness());
					Chromosome chrom =tracker.branchNotCoveredByIndividual.get(trgtBR);
					if(chrom.getBranchDistance()>condTarget.getQuality()){ // Stack example!
						chrom=originalIndividual.clone();
						chrom.setBranchDistance(originalIndividual.getBranchDistance());
						chrom.setFitness(originalIndividual.getFitness());
						
						tracker.branchNotCoveredByIndividual.put(trgtBR,chrom);
					}
				}

			 elapsed = (Calendar.getInstance().getTimeInMillis() - startTime);
//			   if(trgtBR==39)


		}
		return null;
	}

	/** probability to add an operation(s)  */
	private float probAdd = 0.5f;

	/** probability to remove  an operation(s) */
	private float probRemove = 0.2f;

	/**   probability to modify the parameters of  an operation(s)*/
	private float probModify = 0.3f;

	public enum PredicateInformation {
		Field_Constant,Field_Parameter,Field_Field, Parameter_Constant, Parameter_Field, Parameter_Parrameter, No_Info
	}


	public boolean mutate(Chromosome individual,int trgtBR){
		List<Operation> ops = individual.getTest();
		
		final boolean isModifiable = ops.size() >= 0 ;// && this.isPredicateTypePrimitive(trgtBR);

		final boolean isRemovable = ops.size()>3;

		int choice;
		/** 0 => add; 1 => modify; 2 => remove */
		if(isModifiable && isRemovable) {
			float c = ValuesGenerator.getRandomFloat() * (probAdd + probModify + probRemove);
			if(c < probAdd) choice = 0;
			else if(c < probAdd + probModify) choice = 1;
			else choice = 2;
		} else if(isModifiable) {
			float c = ValuesGenerator.getRandomFloat() * (probAdd + probModify);
			if(c < probAdd) choice = 0;
			else choice = 1;
		} else if(isRemovable) {
			float c = ValuesGenerator.getRandomFloat()* (probAdd + probRemove);
			if(c < probAdd) choice = 0;
			else choice = 2;
		} else choice = 0;
		int pos = ValuesGenerator.getRandomInt(ops.size());
		
//		choice=1;
		if(choice==0){
//			System.out.println("add operation");
			addOperationsRandomly(individual,trgtBR);
			return true;
		}else if(choice==1){
//			System.err.println("modify operation ");
			if (ops.get(pos) instanceof CreateObject)
				mutantConstructoParams((CreateObject) ops.get(pos), individual);
			else
				if(ops.get(pos) instanceof Invoke)
					mutantMethodParams((Invoke)ops.get(pos), individual) ;
			
			return false;
		}else if(choice==2){
//			System.out.println("remove operation");			
			int pe=individual.getSize();
//			System.out.println("1- remove operation "+individual.getSize() );
			ops.remove(pos);
//			System.out.println("2-remove operation "+individual.getSize() );
			if(pe<individual.getSize()){
				System.err.println("remove operation pe<individual.getSize");
				System.exit(1);
			}
			return true;
		}else{
			System.err.println("HillCimbing.mutate: undefined operation");
		}
		return true;
	}

	private void addOperationsRandomly(Chromosome individual, int trgtBR) {
	List<Operation> ops = individual.getTest();
		
		int randPos=ValuesGenerator.getRandomIntBetween(0,ops.size()-1);
		int numberMethodsAdded=10 + gaInfo.getAttempt();
		int num = ValuesGenerator.getRandomIntBetween(1,numberMethodsAdded);
		ops = individual.getTest();
		List<Operation> rep = new LinkedList<Operation>();
		rep.addAll(ops.subList(0, randPos));

		for (int i = 0; i < num; i++) {
			Operation newOp=null;
			 newOp=	Operation.randomlyGenerate(gaInfo.getTestCluster());
			if(newOp!=null)
					rep.add(newOp);
			else
					continue;
			}
			rep.addAll(ops.subList(randPos, ops.size()));
			individual.setTest(rep);   	    
	}
	
	private boolean mutantMethodParams(Invoke op,Chromosome individual)  {
		final MethodDeclared method = op.getMethod();
		final Class<?>[] paramsTypes = method.getParameterTypes();
		String[] p = new String[paramsTypes.length];
		String[] preParams=op.getParams();
		boolean commitedChange=false;
		for(int i = 0; i < p.length; i++) {
			Class<?> clz=paramsTypes[i];
			do {
			if(Filter.isPrimitiveOrStringType(clz)){
				p[i] = 	ValuesGenerator.buildValue(clz.getName());
				commitedChange=true;
			}
			else{
				if(GAConfig.NumVarCut==1) break;
					p[i] = Operation.generateRef(method,clz, individual.getReferenceFactory(),individual.getCluster());

				commitedChange=true;
			}
			} while (p[i]==preParams[i]);
		}
		if(commitedChange)
			op.setParams(p);
		return true;
	}
	   private  boolean mutantConstructoParams(CreateObject op,Chromosome individual) {
			final ConstructorDeclared consClazz = op.getConstructor();
			final Class<?>[] paramsTypes = consClazz.getParameterTypes();
			String[] p = new String[paramsTypes.length];
			String[] preParams=consClazz.getParams();
			boolean commitedChange=false;
			for(int i = 0; i < p.length; i++) {
				Class<?> clz=paramsTypes[i];
				do {
				if(Filter.isPrimitiveOrStringType(clz)){
					p[i] = 	ValuesGenerator.buildValue(clz.getName());
					commitedChange=true;
				}
				} while (p[i]==preParams[i]);
			}
			if(commitedChange)
				consClazz.setParams(p);
			return true;
		
	   }


	

	private static void simpleTestExecute(GAInfo     gaInfo,TestClusterBuilder TestCluster){
		  TrackerCoverage track=  TrackerCoverage.getTracker();

			Chromosome individual=ChromosomeFormer.buildNewChromosome(TestCluster);
			System.out.println(individual.toString());
			
			ReflectionExecutor.execute(gaInfo,individual);
			track.calculateFitness(individual);

			System.out.println("------------------------");
			System.out.println(track.toString());
	
	}

	public static void main(String[] args) throws ClassNotFoundException,IOException{
		GAInfo     gaInfo     = new GAInfo();

		long startTime = Calendar.getInstance().getTimeInMillis();
		long elapsed = Calendar.getInstance().getTimeInMillis() - startTime;
		gaInfo.setTestTimeOut( 60 * 1000*10);
		gaInfo.setStartTime(startTime);

		ValuesGenerator.resetSeed(20);	      


		GAConfig.setAlgorithmName("Memetic Algorithm");
		
		 Set<Class<?>> allClasses = new LinkedHashSet<Class<?>>();
			System.out.println("CUT is:" + GAConfig.classFilePath);
			
		      allClasses.addAll(TestClusterBuilder.readClassFromFile());
		      Set<Class<?>> classesToTest = TestClusterBuilder.filterUntestableClasses(allClasses);

				// calculate the test cluster and the set of types involved in the
				TestClusterBuilder TestCluster = new TestClusterBuilder(classesToTest);		
		
		      gaInfo.setTestCluster(TestCluster);
		      ////////////////////////
		      MethodRelations relations = new MethodRelations(classesToTest);
		      relations.buildRelations();
		      gaInfo.setMethodRelations(relations);
			      
		LocalSearch HillClimbing = GAOperationsConfig.getLocalSearch();
	      gaInfo.setMapBranchCondition(HillClimbing.readFileWGZ(gaInfo.getTestCluster()));
	      HillClimbing.setExtraInfo(gaInfo);
		

		try {
			for(int j=0;j<20;j++){
				simpleTestExecute(gaInfo, TestCluster);
			}
				TrackerCoverage tracker = TrackerCoverage.getTracker();
				int no=tracker.branchNotCoveredByIndividual.size();
				for (int i = 0; i <=no -1;i++){
//				for (int i = 0; i <3 ;i++){
					Chromosome improvedIndividual =HillClimbing.execute();
				}
				System.out.println("------------------------");
				System.out.println(tracker.toString());

		//}
				System.exit(0);
				
		} catch (Throwable e) {
			System.err.println("chromFormer.main: " + e);
			System.exit(1);
		}
	}

}